﻿// -----------------------------------------------------------------------
// <copyright file="Collector.cs" company="DarkBone">
// DarkBone
// </copyright>
// -----------------------------------------------------------------------

namespace DarkBone.Engine
{
   using System;
   using System.Collections.Generic;
   using System.Diagnostics;
   using System.IO;
   using System.Linq;
   using System.Threading;

   using DarkBone.Engine.Collections;
   using DarkBone.Engine.EventArgs;
   using DarkBone.Engine.Networking;
   using DarkBone.Engine.SC2;
   using DarkBone.Engine.Tasks;

   /// <summary>
   /// Collects data from SC2 process and sends data over network 
   /// </summary>
   public class Collector
   {
      /// <summary>
      /// Main collector thread
      /// </summary>
      private readonly Thread thrCollect;

      /// <summary>
      /// Networking role
      /// </summary>
      private readonly Role role;

      /// <summary>
      /// process Name
      /// </summary>
      private readonly string processName;

      /// <summary>
      /// Task Executor Class
      /// </summary>
      private TaskExecutor executor;

      /// <summary>
      /// Networking Server
      /// </summary>
      private Server networkingServer;

      /// <summary>
      /// Networking Client
      /// </summary>
      private Client networkingClient;

      /// <summary>
      /// Thread alive flag
      /// </summary>
      private bool alive;

      /// <summary>
      /// Map info
      /// </summary>
      private MapInfo mapInfo;

      /// <summary>
      /// Player collection
      /// </summary>
      private Players players;

      /// <summary>
      /// Unit collection
      /// </summary>
      private Units units;

      /// <summary>
      /// Cache of model units
      /// </summary>
      private Dictionary<int, int> modelCache;

      /// <summary>
      /// Game seconds elapsed
      /// </summary>
      private int gameSecsElapsed;

      // ReSharper disable UnaccessedField.Local

      /// <summary>
      /// Unit Templates
      /// </summary>
      private UnitTemplates unitTemplates;

      /// <summary>
      /// Unit Templates
      /// </summary>
      private MapTemplates mapTemplates;

      /// <summary>
      /// Process ID
      /// </summary>
      private int processId;

      // ReSharper restore UnaccessedField.Local

      /// <summary>
      /// Currently loaded map mpq
      /// </summary>
      private string currentlyLoadedMap;

      /// <summary>
      /// Initializes a new instance of the <see cref="Collector"/> class.
      /// </summary>
      /// <param name="role">
      /// The role.
      /// </param>
      /// <param name="processName">
      /// The process Name.
      /// </param>
      public Collector(Role role, string processName)
      {
         this.role = role;
         this.processName = processName;
         this.thrCollect = new Thread(this.CollectThreadProc);
         this.alive = true;
         this.unitTemplates = new UnitTemplates();
         this.mapTemplates = new MapTemplates();
      }

      /// <summary>
      /// Called when game starts
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void GameStartEventHandler(object sender, EventArgsGame e);

      /// <summary>
      /// Called when game ends
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void GameEndEventHandler(object sender, EventArgsGame e);

      /// <summary>
      /// Called when timer changes
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void GameTimerChangedEventHandler(object sender, EventArgsGameTime e);

      /// <summary>
      /// Called each collect cycle
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void CollectTickEventHandler(object sender, EventArgsCollectTick e);

      /// <summary>
      /// Called when unit is created
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void UnitCreatedEventHandler(object sender, EventArgsUnit e);

      /// <summary>
      /// Called when unit modelnumber is modified
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void UnitModifiedEventHandler(object sender, EventArgsUnit e);

      /// <summary>
      /// Called when unit is destroyed
      /// </summary>
      /// <param name="sender">The sender.</param>
      /// <param name="e">The e.</param>
      public delegate void UnitDestroyedEventHandler(object sender, EventArgsUnit e);

      /// <summary>
      /// Called when game starts
      /// </summary>
      public event GameStartEventHandler OnGameStart;

      /// <summary>
      /// Called when game ends
      /// </summary>
      public event GameEndEventHandler OnGameEnd;

      /// <summary>
      /// Called when game timer changes
      /// </summary>
      public event GameTimerChangedEventHandler OnGameTimerChanged;

      /// <summary>
      /// Called when game timer changes
      /// </summary>
      public event CollectTickEventHandler OnCollectTick;

      /// <summary>
      /// Called when unit is created
      /// </summary>
      public event UnitCreatedEventHandler OnUnitCreated;

      /// <summary>
      /// Called when unit is modified
      /// </summary>
      public event UnitModifiedEventHandler OnUnitModified;

      /// <summary>
      /// Called when unit is destroyed
      /// </summary>
      public event UnitDestroyedEventHandler OnUnitDestroyed;

      /// <summary>
      /// Starts collecting
      /// </summary>
      public void Start()
      {
         Debug.WriteLine("Collector start.");
         this.thrCollect.Start();
      }

      /// <summary>
      /// Stops collecting and networking
      /// </summary>
      public void Stop()
      {
         Debug.WriteLine("Collector stop.");
         this.alive = false;
         if (this.networkingServer != null)
         {
            this.networkingServer.Stop();
         }

         if (this.networkingClient != null)
         {
            this.networkingClient.Stop();
         }
      }

      /// <summary>
      /// Called when game starts
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void GameStart(EventArgsGame e)
      {
         if (this.OnGameStart != null)
         {
            this.OnGameStart(this, e);
         }
      }

      /// <summary>
      /// Called when game ends
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void GameEnd(EventArgsGame e)
      {
         if (this.OnGameEnd != null)
         {
            this.OnGameEnd(this, e);
         }
      }

      /// <summary>
      /// Called when game timer changes
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void GameTimerChanged(EventArgsGameTime e)
      {
         if (this.OnGameTimerChanged != null)
         {
            this.OnGameTimerChanged(this, e);
         }
      }

      /// <summary>
      /// Called when collect completes cycle
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void CollectTick(EventArgsCollectTick e)
      {
         if (this.OnCollectTick != null)
         {
            this.OnCollectTick(this, e);
         }
      }      

      /// <summary>
      /// Called when unit is created
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void UnitCreated(EventArgsUnit e)
      {
         if (this.OnUnitCreated != null)
         {
            this.OnUnitCreated(this, e);
         }
      }

      /// <summary>
      /// Called when unit modify its model
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void UnitModified(EventArgsUnit e)
      {
         if (this.OnUnitModified != null)
         {
            this.OnUnitModified(this, e);
         }
      }

      /// <summary>
      /// Called when unit is destroyed
      /// </summary>
      /// <param name="e">
      /// The e.
      /// </param>
      protected virtual void UnitDestroyed(EventArgsUnit e)
      {
         if (this.OnUnitDestroyed != null)
         {
            this.OnUnitDestroyed(this, e);
         }
      }

      /// <summary>
      /// Reads string from byte array
      /// </summary>
      /// <param name="bytes">
      /// bytes to read from
      /// </param>
      /// <param name="startIndex">
      /// start index
      /// </param>
      /// <returns>
      /// The read string from array.
      /// </returns>
      private static string ReadStringFromArray(byte[] bytes, int startIndex)
      {
         var result = string.Empty;
         if (startIndex >= bytes.Length) 
         {
            return result;
         }

         for (int i = startIndex; i < bytes.Length; i++) 
         {
            if (bytes[i] == 0) 
            { 
               break;
            }

            result += (char)bytes[i];
         }

         return result;
      }

      /// <summary>
      /// Collects the if team color display is on or off.
      /// </summary>
      /// <param name="file">The file.</param>
      /// <returns>
      /// Opened map name
      /// </returns>
      private string CollectMapnameFromMPQ(string file)
      {
         var task = new TaskOpenMPQ(file);
         if (this.networkingClient != null)
         {
            task = this.networkingClient.Query<TaskOpenMPQ>(task);
         }
         else
         {
            this.executor.Execute(task);
         }

         if (!task.Result)
         {
            Debug.WriteLine(@"Task CollectMapnameFromMPQ failed.");
         }

         return task.Output;
      }

      /// <summary>
      /// Thread proc of collectting
      /// </summary>
      private void CollectThreadProc()
      {
         this.executor = new TaskExecutor();
         if (this.role == Role.Server)
         {
            Debug.WriteLine("Collector running as server.");
            this.networkingServer = new Server(this.OnReceive);
            return;
         }

         Debug.WriteLine("Collector running as client.");
         var taskGetProcess = new TaskGetProcess(this.processName);
         this.executor.Execute(taskGetProcess);
         if (!taskGetProcess.Result)
         {
            Debug.WriteLine("Process '{0}' is not on local computer5. Looking around...", this.processName);
            this.networkingClient = new Client();
             var queriedTask = this.networkingClient.Query<TaskGetProcess>(new TaskGetProcess(this.processName));
            if (queriedTask.Result)
            {
               this.processId = queriedTask.ProcessId;
            }
            else
            {
               Debug.WriteLine("Process '{0}' is not found around.", this.processName);
               throw new Exception("Process '{0}' is not found around.");
            }
         } 
         else
         {
            this.processId = taskGetProcess.ProcessId;
         }

         var gameRunning = false;
         while (this.alive)
         {
            var gameTime = this.CollectGameTime();
            if ((gameRunning && (gameTime < 0)) || ((gameTime < this.gameSecsElapsed) && (this.gameSecsElapsed != 0)))
            {
               gameRunning = false;
               this.GameEnd(new EventArgsGame(this.mapInfo, this.gameSecsElapsed));
               this.gameSecsElapsed = 0;
            }

            if (!gameRunning && gameTime >= 0)
            {
               gameRunning = true;
               while (this.alive && this.CollectGameTime() == 0)
               {
                  Thread.Sleep(10);
               }

               this.players = new Players();
               this.units = new Units();
               this.modelCache = new Dictionary<int, int>();
               this.gameSecsElapsed = this.CollectGameTime();
               this.currentlyLoadedMap = this.CollectMapName();
               this.CollectMapInfo(this.currentlyLoadedMap);
               this.GameStart(new EventArgsGame(this.mapInfo, this.gameSecsElapsed));
            }

            if (!gameRunning)
            {
               Thread.Sleep(50);
               continue;
            }

            if (this.gameSecsElapsed < gameTime)
            {
               this.gameSecsElapsed = gameTime;
               this.CollectQueues();
               this.GameTimerChanged(new EventArgsGameTime(this.gameSecsElapsed));
            }

            this.CollectGameData();

            this.CollectTick(new EventArgsCollectTick(this.gameSecsElapsed, this.CollectTeamColor(), this.players, this.units));
         }
      }

      /// <summary>
      /// Collects the queues.
      /// </summary>
      private void CollectQueues()
      {
         for (var iterPlayer = this.players.Count - 1; iterPlayer >= 0; iterPlayer--)
         {
            this.players[iterPlayer].ArmySize.Add(this.players[iterPlayer].PlayerStruct.ArmySize);
         }
      }

      /// <summary>
      /// Collects the name of the map.
      /// </summary>
      /// <returns>Collected name</returns>
      private string CollectMapName()
      {
         var output = this.GetOpenedFiles("-p SC2");
         if (output == null)
         {
            return "Unknown map";
         }
         var lines = output.Split('\n');
         foreach (var filename in from line in lines
                                    where line.Contains(".s2ma")
                                    let bracketIndex = line.LastIndexOf(')') + 3
                                    select line.Substring(bracketIndex + 1, line.Length - bracketIndex - 2))
         {
            return this.CollectMapnameFromMPQ(filename);
         }

         return "Unknown map";
      }

      /// <summary>
      /// Collects the game data.
      /// </summary>
      private void CollectGameData()
      {
         var collectedPlayers = this.CollectPlayers();
         var collectedUnits = this.networkingClient == null ? this.CollectUnits() : this.CollectUnitsFast();

         var currentPlayerSlot = this.ReadMemoryInt8(Memory.PointerCurrentPlayerSlot);
         var currentPlayerTeamslot = 0x10;
         foreach (var playerStruct in collectedPlayers.Where(playerStruct => playerStruct.Index == currentPlayerSlot))
         {
            currentPlayerTeamslot = playerStruct.Teamslot;
            break;
         }

         foreach (var playerNew in collectedPlayers.Select(player => new Player(player)))
         {
            if (currentPlayerTeamslot != 0x10)
            {
               playerNew.Enemy = playerNew.PlayerStruct.Teamslot != currentPlayerTeamslot;
            }
            else
            {
               playerNew.Enemy = playerNew.PlayerStruct.Index != 0;
            }

            this.players.AddOrUpdate(playerNew, this.units.GetUnitsOfOwner(playerNew.PlayerStruct.Index));
         }
            
         foreach (var unitNew in
            (from unit in collectedUnits where unit.ModelNumber != 0 select new Unit(unit)).Where(unitNew => unitNew.UnitStruct.ModelNumber != 0))
         {
            if (this.mapInfo.Template != null)
            {
               unitNew.X = unitNew.UnitStruct.X * this.mapInfo.Template.Background.Width / this.mapInfo.Width;
               unitNew.Y = unitNew.UnitStruct.Y * this.mapInfo.Template.Background.Height / this.mapInfo.Height;
               unitNew.X = (int)(unitNew.X * this.mapInfo.Template.RatioX) + (int)this.mapInfo.Template.MoverX;
               unitNew.Y = this.mapInfo.Template.Background.Height - ((int)(unitNew.Y * this.mapInfo.Template.RatioY) + (int)this.mapInfo.Template.MoverY) + 25;
            }
            
            var index = this.units.IndexOf(unitNew);
            if (index == -1)
            {
               this.units.Add(unitNew, this.gameSecsElapsed);

               this.UnitCreated(new EventArgsUnit(unitNew, this.players));
               continue;
            }

            this.units.Update(index, unitNew, this.gameSecsElapsed, this.OnUnitModified, this.players);
         }

         this.units.DeleteOld(this.gameSecsElapsed, this.OnUnitDestroyed, this.players);
      }

      /// <summary>
      /// Collect game time
      /// </summary>
      /// <returns>
      /// Game time seconds
      /// </returns>
      private int CollectGameTime()
      {
         var gametime = this.ReadMemoryInt32(Memory.PointerGameTime);
         return gametime >> 4;
      }

      /// <summary>
      /// Collects the if team color display is on or off.
      /// </summary>
      /// <returns>True if on</returns>
      private bool CollectTeamColor()
      {
         return 2 == this.ReadMemoryInt8(Memory.PointerTeamColor);
      }

      /// <summary>
      /// Collects information about current map
      /// </summary>
      /// <param name="mapName">Name of the map.</param>
      private void CollectMapInfo(string mapName)
      {
         var bytes = this.ReadMemory(Memory.PointerMap, 0x30);
         this.mapInfo.Width = BitConverter.ToInt32(bytes, 0x0);
         this.mapInfo.Height = BitConverter.ToInt32(bytes, 0x4);
         this.mapInfo.Left = BitConverter.ToInt32(bytes, 0x20);
         this.mapInfo.Bottom = BitConverter.ToInt32(bytes, 0x24);
         this.mapInfo.Right = BitConverter.ToInt32(bytes, 0x28);
         this.mapInfo.Top = BitConverter.ToInt32(bytes, 0x2C);

         this.mapInfo.Template = MapTemplates.GetTemplateByMapName(mapName);
         Debug.WriteLine("MapInfo: " + mapName + " => " + this.mapInfo.Template.Filename);
      }

      // ReSharper disable PossibleInvalidCastException

      /// <summary>
      /// Collects all units
      /// </summary>
      /// <returns>
      /// Collected units
      /// </returns>
      private IEnumerable<Memory.UnitStruct> CollectUnits()
      {
         var numOfUnits = this.ReadMemoryInt16(Memory.PointerUnitsNum);
         var unitPointer = 0;
         var unitCounter = 0;
         var collectedUnits = new Memory.UnitStruct[numOfUnits];
         var firstUnitFound = false;
         while ((numOfUnits > 0) && (unitCounter < numOfUnits))
         {
            var unitStruct = this.ReadMemory(Memory.PointerUnits + (unitPointer * Memory.UNITSTRUCTSIZE), Memory.UNITSTRUCTSIZE);
            if (!firstUnitFound)
            {
               if ((unitStruct[0x04] == 0xFF) && (unitStruct[0x05] == 0xFF))
               {
                  firstUnitFound = true;
               }
               else
               {
                  ++unitPointer;
                  continue;
               }
            }

            collectedUnits[unitCounter] = new Memory.UnitStruct
               {
                  Id = (short)(BitConverter.ToInt16(unitStruct, 0x02) >> 2),
                  X = BitConverter.ToInt32(unitStruct, 0x40),
                  Y = BitConverter.ToInt32(unitStruct, 0x44),
                  ModelNumber = this.ReadMemoryInt32((IntPtr)((BitConverter.ToInt32(unitStruct, 0x08) << 5) + 0x18)),
                  Owner = unitStruct[0x27],                  
               };
            if ((unitStruct[0x06] == 0xFF) && (unitStruct[0x07] == 0xFF))
            {
               break;
            }

            unitPointer = (short)(unitStruct[0x06] | (unitStruct[0x07] << 8));
            ++unitCounter;
         }

         return collectedUnits;
      }

      /// <summary>
      /// Collects all units
      /// </summary>
      /// <returns>Collected units</returns>
      private IEnumerable<Memory.UnitStruct> CollectUnitsFast()
      {
         var numOfUnits = this.ReadMemoryInt16(Memory.PointerUnitsNum);
         var unitPointer = 0;
         var unitCounter = 0;
         var collectedUnits = new Memory.UnitStruct[numOfUnits];
         var unitStructs = this.ReadMemory(Memory.PointerUnits, Memory.UNITSTRUCTSIZE * numOfUnits);
         var unitStruct = new byte[Memory.UNITSTRUCTSIZE];
         var firstUnitFound = false;
         while ((numOfUnits > 0) && (unitCounter < numOfUnits) && (unitPointer < numOfUnits))
         {
            Array.Copy(unitStructs, unitPointer * Memory.UNITSTRUCTSIZE, unitStruct, 0, Memory.UNITSTRUCTSIZE);
            if (!firstUnitFound)
            {
               if ((unitStruct[0x04] == 0xFF) && (unitStruct[0x05] == 0xFF))
               {
                  firstUnitFound = true;
               }
               else
               {
                  ++unitPointer;
                  continue;
               }
            }

            var modelptr = (BitConverter.ToInt32(unitStruct, 0x08) << 5) + 0x18;
            int modelNumber;
            if (this.modelCache.ContainsKey(modelptr))
            {
               modelNumber = this.modelCache[modelptr];
            }
            else
            {
               modelNumber = this.ReadMemoryInt32((IntPtr)modelptr);
               this.modelCache.Add(modelptr, modelNumber);
            }

            collectedUnits[unitCounter] = new Memory.UnitStruct 
               {
                  Id = (short)(BitConverter.ToInt16(unitStruct, 0x02) >> 2),
                  X = BitConverter.ToInt32(unitStruct, 0x40),
                  Y = BitConverter.ToInt32(unitStruct, 0x44),
                  ModelNumber = modelNumber,
                  Owner = unitStruct[0x27],                  
               };
            if ((unitStruct[0x06] == 0xFF) && (unitStruct[0x07] == 0xFF))
            {
               break;
            }

            unitPointer = (short)(unitStruct[0x06] | (unitStruct[0x07] << 8));
            ++unitCounter;
         }

         return collectedUnits;
      }

      /// <summary>
      /// Collects all players
      /// </summary>
      /// <returns>Collected players</returns>
      private IEnumerable<Memory.PlayerStruct> CollectPlayers()
      {
         var collectedPlayers = new Memory.PlayerStruct[Memory.NUMOFPLAYERS];
         var playersStruct = this.ReadMemory(Memory.PointerPlayers, Memory.PLAYERSTRUCTSIZE * Memory.NUMOFPLAYERS);
         var playerCounter = 0;
         do
         {
            var offset = playerCounter * Memory.PLAYERSTRUCTSIZE;
            collectedPlayers[playerCounter] = new Memory.PlayerStruct
               {
                  Status = playersStruct[offset],
                  Name = ReadStringFromArray(playersStruct, offset + 0x44),
                  Type = playersStruct[0x1D + offset],
                  ColorIndex = playersStruct[0x9C + offset],
                  Index = playerCounter,
                  Teamslot = playersStruct[0x1C + offset],
                  ArmySize = BitConverter.ToInt32(playersStruct, 0x750 + offset) + BitConverter.ToInt32(playersStruct, 0x770 + offset),
                  Account =
                     (playerCounter > 0)
                        ? this.ReadMemoryString(
                           (IntPtr)
                           ((int)Memory.PointerPlayersAccount + ((playerCounter - 1) * Memory.PLAYERACCOUNTSTRUCTSIZE)))
                        : string.Empty,
               };
         }
         while (++playerCounter < Memory.NUMOFPLAYERS);

         return collectedPlayers;
      }

      // ReSharper restore PossibleInvalidCastException

      /// <summary>
      /// Collect console from Handle.exe utility
      /// </summary>
      /// <param name="argument">The argument.</param>
      /// <returns>
      /// Returns stdout
      /// </returns>
      private string GetOpenedFiles(string argument)
      {
         var task = new TaskGetOpenedFiles(argument);
         if (this.networkingClient != null)
         {
            task = this.networkingClient.Query<TaskGetOpenedFiles>(task);
         }
         else
         {
            this.executor.Execute(task);
         }

         if (!task.Result)
         {
            Debug.WriteLine(@"Task TaskGetOpenedFiles failed.");
         }

         return task.Output;
      }

      /// <summary>
      /// Collect array of bytes from process
      /// </summary>
      /// <param name="addressToRead">address to read from</param>
      /// <param name="bytesToRead">num of bytes to read</param>
      /// <returns>Returns array of bytes from process</returns>
      private byte[] ReadMemory(IntPtr addressToRead, int bytesToRead)
      {
         var task = new TaskReadMemory(addressToRead, bytesToRead);
         if (this.networkingClient != null)
         {
            task = this.networkingClient.Query<TaskReadMemory>(task);
         }
         else
         {
            this.executor.Execute(task);
         }

         if (!task.Result)
         {
            Debug.WriteLine(@"Task read memory failed reading 0x{0}, num of bytes 0x{1}", addressToRead.ToString("X"), bytesToRead.ToString("X"));
         }

         return task.Bytes;
      }

      /// <summary>
      /// Read 1 byte at address
      /// </summary>
      /// <param name="addressToRead">
      /// The address to read.
      /// </param>
      /// <returns>
      /// byte read
      /// </returns>
      private byte ReadMemoryInt8(IntPtr addressToRead)
      {
         return this.ReadMemory(addressToRead, 1)[0];
      }

      /// <summary>
      /// Read 2 bytes at address
      /// </summary>
      /// <param name="addressToRead">
      /// The address to read.
      /// </param>
      /// <returns>
      /// bytes converted to short 
      /// </returns>
      private short ReadMemoryInt16(IntPtr addressToRead)
      {
         var bytes = this.ReadMemory(addressToRead, 2);
         return (short)(bytes[0] | (bytes[1] << 8));
      }

      /// <summary>
      /// Read 4 bytes at address
      /// </summary>
      /// <param name="addressToRead">
      /// The address to read.
      /// </param>
      /// <returns>
      /// bytes converted to int
      /// </returns>
      private int ReadMemoryInt32(IntPtr addressToRead)
      {
         var bytes = this.ReadMemory(addressToRead, 4);
         return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
      }

      /// <summary>
      /// Read string from address
      /// </summary>
      /// <param name="addressToRead">
      /// The address to read.
      /// </param>
      /// <returns>
      /// String read
      /// </returns>
      private string ReadMemoryString(IntPtr addressToRead)
      {
         var task = new TaskReadMemoryString(addressToRead);
         if (this.networkingClient != null)
         {
            task = this.networkingClient.Query<TaskReadMemoryString>(task);
         }
         else
         {
            this.executor.Execute(task);   
         }

         if (!task.Result)
         {
            Debug.WriteLine(@"Task read memory failed reading 0x{0}, num of bytes 0x{1}", addressToRead.ToString("X"));
         }

         return task.Value;
      }

      /// <summary>
      /// Called when client request execution
      /// </summary>
      /// <param name="obj">object to be executed</param>
      /// <returns>executed task</returns>
      private object OnReceive(object obj)
      {
         this.executor.Execute((Task)obj);
         return obj;
      }
   }
}
